package redislib

import (
	"context"
	"fmt"
	"time"

	"gitee.com/night-tc/gobige/logger"
	"gitee.com/night-tc/gobige/threads"
	"github.com/bsm/redislock"
)

const (
	// 锁时间长度，单位为秒
	LOCK_TIMEOUT = 5
)

type Locker struct {
	LockKey   string                   // 锁的键
	LockValue string                   // 锁的值
	timeout   time.Duration            // 锁的超时时间
	redisTran *redislock.Lock          // Redis锁对象
	getRedisF func() *RedisHandleModel // 获取 Redis 操作的方法
	thgo      *threads.SubThreadGo     // 用于管理子线程的工具
}

// 创建一个新的 Locker 实例
func NewLocker(key, val string, getRedisF func() *RedisHandleModel, thgo *threads.SubThreadGo) *Locker {
	result := &Locker{
		LockKey:   key,
		LockValue: val,
		timeout:   LOCK_TIMEOUT * time.Second, // 设置默认超时时间
		getRedisF: getRedisF,
		thgo:      thgo,
	}
	return result
}

// 返回 Locker 的字符串表示
func (this *Locker) String() string {
	return fmt.Sprint(this.LockKey, this.LockValue)
}

// 尝试获取锁，成功返回 true，失败返回 false
func (this *Locker) TryLock() bool {
	rdb := this.getRedisF() // 获取 Redis 客户端
	redislocker := redislock.New(rdb.Client)
	RedisTran, err := redislocker.Obtain(rdb.Ctx, this.LockKey, this.timeout, &redislock.Options{
		Token: this.LockValue, // 使用指定的 Token
	})
	if err == redislock.ErrNotObtained {
		// 锁未获取到
		return false
	} else if err != nil {
		// 记录获取锁时的错误
		logger.Warnf("redisLocker.TryLock:%s %s err:%s", this.LockKey, this.LockValue, err)
		return false
	}
	this.redisTran = RedisTran // 保存锁对象
	logger.Debugf(logger.LogKey_Redis, "redisLocker.TryLock true:%s %s", this.LockKey, this.LockValue)
	this.thgo.Go(this.keep) // 启动子线程保持锁
	return true
}

// 释放锁
func (this *Locker) Unlock() bool {
	this.thgo.Cal()                                     // 停止保持锁的子线程
	err := this.redisTran.Release(context.Background()) // 释放锁
	if err != nil {
		// 记录释放锁时的错误
		logger.Warnf("redisLocker.Unlock:%s %s err:%s", this.LockKey, this.LockValue, err)
	}
	this.LockValue = "" // 清空锁的值
	return true
}

// 阻塞式获取锁，直到成功或发生错误
func (this *Locker) Lock() {
	rdb := this.getRedisF() // 获取 Redis 客户端
	redislocker := redislock.New(rdb.Client)
	RedisTran, err := redislocker.Obtain(rdb.Ctx, this.LockKey, this.timeout, &redislock.Options{
		RetryStrategy: redislock.LinearBackoff(100 * time.Millisecond), // 设置重试策略
		Token:         this.LockValue,
	})
	if err != nil {
		// 获取锁失败时抛出错误
		panic(err)
	}
	this.redisTran = RedisTran // 保存锁对象
	logger.Debugf(logger.LogKey_Redis, "redisLocker.Lock true:%s %s", this.LockKey, this.LockValue)
	this.thgo.Go(this.keep) // 启动子线程保持锁
}

// 带上下文的锁获取方法，支持取消操作
func (this *Locker) LockCtx(ctx context.Context) bool {
	rdb := this.getRedisF() // 获取 Redis 客户端
	redislocker := redislock.New(rdb.Client)
	RedisTran, err := redislocker.Obtain(ctx, this.LockKey, this.timeout, &redislock.Options{
		RetryStrategy: redislock.LinearBackoff(100 * time.Millisecond), // 设置重试策略
		Token:         this.LockValue,
	})
	if err == redislock.ErrNotObtained {
		// 锁未获取到
		return false
	} else if err != nil {
		// 记录获取锁时的错误
		logger.Warnf("redisLocker.LockCtx:%s %s err:%s", this.LockKey, this.LockValue, err)
		return false
	}
	this.redisTran = RedisTran // 保存锁对象
	logger.Debugf(logger.LogKey_Redis, "redisLocker.LockCtx true:%s %s", this.LockKey, this.LockValue)
	this.thgo.Go(this.keep) // 启动子线程保持锁
	return true
}

// 保持锁的有效性，通过定期刷新锁的过期时间
func (this *Locker) keep(ctx context.Context) {
	RedisTran := this.redisTran
	for {
		select {
		case <-ctx.Done():
			// 上下文取消时退出
			return
		default:
			// 刷新锁的过期时间
			if err := RedisTran.Refresh(ctx, this.timeout, nil); err != nil {
				// 记录刷新锁时的错误
				logger.Warnf("redisLocker.keep:%s %s err:%s", this.LockKey, this.LockValue, err)
			} else {
				time.Sleep(this.timeout / 2) // 等待一段时间后再次刷新
			}
		}
	}
}
